<template>
  <div
    ref="textareaRef"
    class="anti-template"
    :contenteditable="true"
    :style="style"
    @click="onClick"
    @keyup.prevent="onKeyup"
    @paste.prevent="onPaste"
  >
  </div>
</template>

<script setup lang="ts">
  import { buildUUID } from '@/utils/uuid';
  import { ref, unref, PropType, StyleValue } from 'vue';

  defineProps({
    style: Object as PropType<StyleValue>,
  });

  const textareaRef = ref<HTMLDivElement | null>(null);
  const lastEditRange = ref<Range>();

  const onClick = () => {
    const selection = getSelection() as Selection;
    lastEditRange.value = selection.getRangeAt(0);
  };

  const onKeyup = () => {
    const selection = getSelection() as Selection;
    lastEditRange.value = selection.getRangeAt(0);
  };

  const onPaste = (e: ClipboardEvent) => {
    const text = e.clipboardData?.getData('text/plain') as string;
    const lastChild = resetTemplate(text);
    setRange(lastChild, true, false);
    // const text = document.createTextNode(e.clipboardData?.getData('text/plain') as string);
    // setRange(text);
  };

  function getLength(str: string) {
    var realLength = 0,
      len = str.length,
      charCode = -1;
    for (var i = 0; i < len; i++) {
      charCode = str.charCodeAt(i);
      if (charCode >= 0 && charCode <= 128) realLength += 1;
      else realLength += 2;
    }
    return realLength;
  }

  function createKeywordInput(keyword: string) {
    const keywordInput = document.createElement('input');
    const id = buildUUID();
    keywordInput.setAttribute('data-id', id);
    keywordInput.setAttribute('disabled', 'false');
    keywordInput.className = 'edit-input';
    keywordInput.style.textAlign = 'center';
    keywordInput.style.width = getLength(keyword) * 7 + 'px';
    keywordInput.value = keyword;
    return keywordInput;
  }

  function setRange(val: any, isClear: boolean = false, isInsert: boolean = true) {
    const selection = getSelection() as Selection;
    if (unref(lastEditRange)) {
      isClear && selection.deleteFromDocument();
      selection.removeAllRanges();
      selection.addRange(unref(lastEditRange) as Range);
    }

    const range = selection.getRangeAt(0);
    isInsert && range.insertNode(val);
    range.setStartAfter(val);
    selection.removeAllRanges();
    selection.addRange(range);
    lastEditRange.value = selection.getRangeAt(0);
  }

  function keywordSubstr(parentNode: HTMLDivElement, str: string) {
    let remain = str;
    let index = str.indexOf('{');
    let keywordLastIndex = str.indexOf('}');
    const subStr = index === -1 ? str : str.substring(0, index);
    if (subStr) {
      const text = document.createTextNode(subStr);
      remain = remain.replace(subStr, '');
      parentNode.appendChild(text);
    }
    if (index !== -1 && keywordLastIndex !== -1) {
      const subStr = str.substring(index, keywordLastIndex + 1);
      const input = createKeywordInput(subStr);
      remain = remain.replace(subStr, '');
      parentNode.appendChild(input);
    }
    if (remain) {
      keywordSubstr(parentNode, remain);
    }
  }

  function resetTemplate(text: string) {
    const parentNode = unref(textareaRef) as HTMLDivElement;
    parentNode.focus();
    text.split('\n').forEach((v, i) => {
      if (i > 0) {
        const div = document.createElement('div');
        parentNode.appendChild(div);
        keywordSubstr(div, v);
      } else {
        keywordSubstr(parentNode, v);
      }
    });
    let lastChild = parentNode.lastChild;
    if (lastChild?.nodeName.toLowerCase() === 'div') {
      lastChild = lastChild.lastChild;
    }
    return lastChild;
  }

  function resetGet(dom: HTMLDivElement, str: string = '') {
    let val = str;
    dom?.childNodes.forEach((v) => {
      const nodeName = v.nodeName;
      if (nodeName.toLowerCase() === 'input') {
        val += (v as any).value;
      } else if (nodeName.toLowerCase() === 'div') {
        val += '\n';
        val = resetGet(v as HTMLDivElement, val);
      } else if (nodeName.toLowerCase() === '#text') {
        val += v.nodeValue;
      }
    });
    return val;
  }

  function addKeyword(keyword: string) {
    if (!unref(textareaRef)) return;
    const dom = unref(textareaRef);

    dom?.focus();
    const keywordInput = createKeywordInput(keyword);
    setRange(keywordInput);
  }

  function getVal(): string {
    const dom = unref(textareaRef) as HTMLDivElement;
    const val = resetGet(dom);
    return val;
  }

  function handleBackfill(text: string) {
    const lastChild = resetTemplate(text);
    setRange(lastChild, false, false);
  }

  defineExpose({
    addKeyword,
    getVal,
    handleBackfill,
  });
</script>

<style lang="less">
  .anti-template {
    width: 600px;
    height: 200px;
    background-color: #fff;
    border: 1px solid #eee;
    padding: 15px;
    overflow-y: scroll;
  }

  /*关键词样式*/
  .edit-input {
    background: #fff;
    border: 0;
    vertical-align: unset;
    text-align: center;
    color: @primary-color;
  }
</style>
